package com.hontech.bv.conn.usb

import android.hardware.usb.*
import com.hontech.bv.codec.toUint8
import com.hontech.bv.conn.Proto
import com.hontech.bv.conn.SerialPort
import com.hontech.bv.utils.log
import java.io.IOException


class FtdiSerialDevice(val device: UsbDevice, val conn: UsbDeviceConnection) : SerialPort {

    private enum class DeviceType {
        TYPE_BM, TYPE_AM, TYPE_2232C, TYPE_R, TYPE_2232H, TYPE_4232H
    }

    companion object {

        const val VID = 0x0403
        const val PID = 0x6001

        const val DATABITS_5 = 5
        const val DATABITS_6 = 6
        const val DATABITS_7 = 7
        const val DATABITS_8 = 8

        const val FLOWCONTROL_NONE = 0
        const val FLOWCONTROL_RTSCTS_IN = 1
        const val FLOWCONTROL_RTSCTS_OUT = 2
        const val FLOWCONTROL_XONXOFF_IN = 4
        const val FLOWCONTROL_XONXOFF_OUT = 8

        const val PARITY_NONE = 0
        const val PARITY_ODD = 1
        const val PARITY_EVEN = 2
        const val PARITY_MARK = 3
        const val PARITY_SPACE = 4
        const val STOPBITS_1 = 1
        const val STOPBITS_1_5 = 3
        const val STOPBITS_2 = 2

        const val USB_WRITE_TIMEOUT_MILLIS = 5000
        const val USB_READ_TIMEOUT_MILLIS = 0

        const val USB_TYPE_STANDARD = 0x00 shl 5
        const val USB_TYPE_CLASS = 0x00 shl 5
        const val USB_TYPE_VENDOR = 0x00 shl 5
        const val USB_TYPE_RESERVED = 0x00 shl 5

        const val USB_RECIP_DEVICE = 0x00
        const val USB_RECIP_INTERFACE = 0x01
        const val USB_RECIP_ENDPOINT = 0x02
        const val USB_RECIP_OTHER = 0x03

        const val USB_ENDPOINT_IN = 0x80
        const val USB_ENDPOINT_OUT = 0x00

        /**
         * Reset the port.
         */
        private const val SIO_RESET_REQUEST = 0

        /**
         * Set the modem control register.
         */
        private const val SIO_MODEM_CTRL_REQUEST = 1

        /**
         * Set flow control register.
         */
        private const val SIO_SET_FLOW_CTRL_REQUEST = 2

        /**
         * Set baud rate.
         */
        private const val SIO_SET_BAUD_RATE_REQUEST = 3

        /**
         * Set the data characteristics of the port.
         */
        private const val SIO_SET_DATA_REQUEST = 4

        private const val SIO_RESET_SIO = 0
        private const val SIO_RESET_PURGE_RX = 1
        private const val SIO_RESET_PURGE_TX = 2

        const val FTDI_DEVICE_OUT_REQTYPE =
            UsbConstants.USB_TYPE_VENDOR or USB_RECIP_DEVICE or USB_ENDPOINT_OUT
        const val FTDI_DEVICE_IN_REQTYPE =
            UsbConstants.USB_TYPE_VENDOR or USB_RECIP_DEVICE or USB_ENDPOINT_IN

        const val FTDI_INTERFACE_ID = 0
        const val FTDI_ENDPOINT_OUT = 2
        const val FTDI_ENDPOINT_IN = 129

        fun isSupport(device: UsbDevice): Boolean {
            return (device.productId == PID) && (device.vendorId == VID)
        }
    }

    private var mType: DeviceType? = null
    private var mInterface: UsbInterface? = null
    private var mEpIn: UsbEndpoint? = null
    private var mEpOut: UsbEndpoint? = null
    private var maxInSize: Int = 0
    private var maxOutSize: Int = 0

    private var readDataBuf = byteArrayOf()
    private var readDataSize = 0
    private var readDataIndex = 0

    init {
        open()
        setParameters(
            115200,
            DATABITS_8,
            STOPBITS_1,
            PARITY_NONE
        )
    }

    override fun close() {
    }

    override fun notifyClose() {
        log("USB关闭")
        if (mInterface != null) {
            conn.releaseInterface(mInterface)
        }
        conn.close()
    }

    override fun readBytes(buf: ByteArray): Int {
        val size = buf.size
        var len = size
        var index = 0
        while (true) {
            val n = readDataSize - readDataIndex
            if (n >= len) {
                System.arraycopy(readDataBuf, readDataIndex, buf, index, len)
                readDataIndex += len
                return size
            }
            if (n != 0) {
                System.arraycopy(readDataBuf, readDataIndex, buf, index, n)
                readDataIndex += n
                len -= n
                index += n
            }
            readData()
        }
    }

    override fun sync(): Int {
        var flag = false
        while (true) {
            val v = readByte()
            if (flag && (v == Proto.HEAD1)) {
                break
            }
            flag = v == Proto.HEAD0
        }
        return readByte()
    }

    private fun open() {
        emulateInterface()
        reset()
    }

    private fun readByte(): Int {
        while (true) {
            val n = readDataSize - readDataIndex
            if (n >= 1) {
                val value = readDataBuf[readDataIndex].toUint8()
                readDataIndex += 1
                return value
            }
            readData()
        }
    }

    private fun readData() {
        val n = conn.bulkTransfer(mEpIn, readDataBuf, maxInSize, USB_READ_TIMEOUT_MILLIS)
        if (n < 0) {
            throw IOException("usb 读取错误 缓冲区异常或者已经断开")
        }
        if (n < 2) {
            throw IOException("未知错误")
        }
        readDataSize = n
        readDataIndex = 2
    }

    override fun write(buf: ByteArray) {
        var index = 0
        val size = buf.size
        while (index < size) {
            val n = size - index
            val len = if (n > maxOutSize) maxOutSize else n
            val ret = conn.bulkTransfer(mEpOut, buf, index, len, USB_WRITE_TIMEOUT_MILLIS)
            if (ret <= 0) {
                throw IOException("USB设备已经断开")
            }
            index += ret
        }
    }

    private fun reset() {
        val result = conn.controlTransfer(
            FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
            SIO_RESET_SIO, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS
        )
        if (result != 0) {
            throw IOException("Reset failed: result=$result")
        }
        mType = DeviceType.TYPE_R
    }

    private fun setBaudRate(baudRate: Int): Int {
        val vals = convertBaudrate(baudRate)
        val actualBaudrate = vals[0]
        val index = vals[1]
        val value = vals[2]
        val result = conn.controlTransfer(
            FTDI_DEVICE_OUT_REQTYPE, SIO_SET_BAUD_RATE_REQUEST,
            value.toInt(), index.toInt(), null, 0, USB_WRITE_TIMEOUT_MILLIS
        )
        if (result != 0) {
            throw IOException("Setting baudrate failed: result=$result")
        }
        return actualBaudrate.toInt()
    }

    fun setParameters(baudRate: Int, dataBits: Int, stopBits: Int, parity: Int) {
        setBaudRate(baudRate)
        var config = dataBits
        when (parity) {
            PARITY_NONE -> config = config or (0x00 shl 8)
            PARITY_ODD -> config = config or (0x01 shl 8)
            PARITY_EVEN -> config = config or (0x02 shl 8)
            PARITY_MARK -> config = config or (0x03 shl 8)
            PARITY_SPACE -> config = config or (0x04 shl 8)
            else -> throw IllegalArgumentException("Unknown parity value: $parity")
        }

        when (stopBits) {
            STOPBITS_1 -> config = config or (0x00 shl 11)
            STOPBITS_1_5 -> config = config or (0x01 shl 11)
            STOPBITS_2 -> config = config or (0x02 shl 11)
            else -> throw IllegalArgumentException("Unknown stopBits value: $stopBits")
        }

        val result = conn.controlTransfer(
            FTDI_DEVICE_OUT_REQTYPE, SIO_SET_DATA_REQUEST, config,
            0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS
        )
        if (result != 0) {
            throw IOException("Setting parameters failed: result=$result")
        }
    }

    private fun convertBaudrate(baudrate: Int): LongArray {
        val divisor = 24000000 / baudrate
        var bestDivisor = 0
        var bestBaud = 0
        var bestBaudDiff = 0
        val fracCode = intArrayOf(0, 3, 2, 4, 1, 5, 6, 7)
        for (i in 0..1) {
            var tryDivisor = divisor + i
            val baudEstimate: Int
            val baudDiff: Int
            if (tryDivisor <= 8) {
                // Round up to minimum supported divisor
                tryDivisor = 8
            } else if (mType !== DeviceType.TYPE_AM && tryDivisor < 12) {
                // BM doesn't support divisors 9 through 11 inclusive
                tryDivisor = 12
            } else if (divisor < 16) {
                // AM doesn't support divisors 9 through 15 inclusive
                tryDivisor = 16
            } else {
                if (mType === DeviceType.TYPE_AM) {
                    // TODO
                } else {
                    if (tryDivisor > 0x1FFFF) {
                        // Round down to maximum supported divisor value (for
                        // BM)
                        tryDivisor = 0x1FFFF
                    }
                }
            }
            // Get estimated baud rate (to nearest integer)
            baudEstimate = (24000000 + tryDivisor / 2) / tryDivisor

            // Get absolute difference from requested baud rate
            if (baudEstimate < baudrate) {
                baudDiff = baudrate - baudEstimate
            } else {
                baudDiff = baudEstimate - baudrate
            }

            if (i == 0 || baudDiff < bestBaudDiff) {
                // Closest to requested baud rate so far
                bestDivisor = tryDivisor
                bestBaud = baudEstimate
                bestBaudDiff = baudDiff
                if (baudDiff == 0) {
                    // Spot on! No point trying
                    break
                }
            }
        }
        // Encode the best divisor value
        var encodedDivisor = (bestDivisor shr 3 or (fracCode[bestDivisor and 7] shl 14)).toLong()
        // Deal with special cases for encoded value
        if (encodedDivisor == 1L) {
            encodedDivisor = 0 // 3000000 baud
        } else if (encodedDivisor == 0x4001L) {
            encodedDivisor = 1 // 2000000 baud (BM only)
        }

        // Split into "value" and "index" values
        val value = encodedDivisor and 0xFFFF
        var index: Long
        if (mType === DeviceType.TYPE_2232C || mType === DeviceType.TYPE_2232H || mType === DeviceType.TYPE_4232H) {
            index = encodedDivisor shr 8 and 0xffff
            index = index and 0xFF00
            index = index or 0 /* TODO mIndex */
        } else {
            index = encodedDivisor shr 16 and 0xffff
        }
        return longArrayOf(bestBaud.toLong(), index, value)
    }

    private fun emulateInterface() {
        val count = device.interfaceCount
        mInterface = null
        for (i in 0 until count) {
            val inter = device.getInterface(i)
            log("Interface:${inter.id}")
            if (inter.id == FTDI_INTERFACE_ID) {
                mInterface = inter
                val ret = conn.claimInterface(mInterface, true)
                if (!ret) {
                    throw IOException("claim Interface error :$ret")
                }
                emulateEndpoint(inter)
                break
            }
        }
    }

    private fun emulateEndpoint(inter: UsbInterface) {
        mEpIn = null
        mEpOut = null
        val count = inter.endpointCount
        for (i in 0 until count) {
            val ep = inter.getEndpoint(i)
            if (ep.address == FTDI_ENDPOINT_IN) {
                mEpIn = ep
            } else if (ep.address == FTDI_ENDPOINT_OUT) {
                mEpOut = ep
            }
            log("Endpoint:${ep.address}  ${ep.type} ${ep.direction == UsbConstants.USB_DIR_OUT}")
            if ((mEpIn != null) && (mEpOut != null)) {
                maxOutSize = mEpOut!!.maxPacketSize
                maxInSize = mEpIn!!.maxPacketSize
                readDataBuf = ByteArray(maxInSize)
                break
            }
        }
        if ((mEpIn == null) || (mEpOut == null)) {
            throw IOException("无法匹配端口")
        }
    }
}
